package org.sch.util;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @program: javatest
 * @description:
 * @author: sch
 * @create: 2020-06-14 18:56
 **/
public class SchIocContentUtils {
    private Map<String, Object> iocMap = new HashMap<>();

    public SchIocContentUtils(String configPath) {
        parseXml(configPath);
    }

    /**
     * 解析xml，生成一个ioc容器。
     * 支持：setter,constructor,自动装配。
     * 思路：
     *  1，dom4j解析xml获取各元素
     *  2，遍历一级子元素放入iocMap中 id--key,class生成的Object对象为value
     *      2.1根据xml的id和class获取类的Class对象，准备为后面的实例化对象和依赖注入作准备
     *      2.2维护依赖关系：
     *          遍历二级子元素，判断否包含property标签,如果包含用setter注入，如果不包含用constructor方式注入
     *  3，判断是否要自动装配，如果需要
     *      3.1byteType装配
     *          1，遍历属性
     *          2，遍历map
     *          3，判断map中的对象父接口是否与属性相同，如果相同则装配
     *      3.2byName装配
     *          1，遍历属性
     *          2，iocMap.get(属性名)，结果不为空则装配
     *
     * @param configPath
     */
    private void parseXml(String configPath) {
        //1,加载文件
        File file = new File(this.getClass().getResource("/").getPath() + "/" + configPath);
        //2,解析xml
        SAXReader reader = new SAXReader();
        try {
            boolean autowireFlag = false;
            Document document = reader.read(file);
            Element rootElement = document.getRootElement();
            Attribute attribute = rootElement.attribute("default-autowire");
            String autowireType = attribute.getValue();
            if (!StringUtils.isEmpty(autowireType)) {
                autowireFlag = true;
            }
            for (Iterator<Element> it = rootElement.elementIterator(); it.hasNext(); ) {
                Element elementChild1 = it.next();
                //step1:实例化对象
                Attribute att1 = elementChild1.attribute("id");
                String beanId = att1.getValue();
                Attribute att2 = elementChild1.attribute("class");
                String beanClass = att2.getValue();

                //step2:维护依赖关系
                //获取bean
                Class<?> aClass = Class.forName(beanClass);

                //<property name="userService" ref="userDao"/>
                Object obj = null;
                for (Iterator<Element> iteratorSecond = elementChild1.elementIterator(); iteratorSecond.hasNext(); ) {
                    Element elementChild2 = iteratorSecond.next();
                    //判断是否有property对象
                    if ("property".equals(elementChild2.getName())) {
                        obj = aClass.newInstance();
                        //获取property中的name和ref,name:是类中属性名字，ref：是容器中的key
                        Attribute refAttr = elementChild2.attribute("ref");
                        Object injectObj = iocMap.get(refAttr.getValue());
                        Attribute nameAttr = elementChild2.attribute("name");
                        Field field = aClass.getDeclaredField(nameAttr.getValue());
                        field.setAccessible(true);
                        field.set(obj, injectObj);
                    } else {
                        //构造函数
                        //ref的值是容器中的key
                        Attribute refAttr = elementChild2.attribute("ref");
                        String injectStr = refAttr.getValue();
                        Object injectObj = iocMap.get(injectStr);
                        Constructor constructor = aClass.getConstructor(injectObj.getClass().getInterfaces()[0]);
                        obj = constructor.newInstance(injectObj);
                    }
                }

                if (autowireFlag && obj == null) {
                    obj = aClass.newInstance();
                    Field[] fieldList = aClass.getDeclaredFields();
                    //自动装配
                    /**
                     * 由於是bytype 所以需要遍历map当中的所有对象
                     * 判断对象的类型是不是和这个injectObjectClazz相同
                     */
                    if ("byType".equals(autowireType)) {
                        //根据类型自动装配
                        //1,判断类型,与ioc中的对象匹配
                        for (Field field : fieldList) {
                            field.setAccessible(true);
                            Class fieldClazz = field.getType();
                            int count = 0;
                            Object injectObj = null;
                            for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
                                injectObj = entry.getValue();
                                if (fieldClazz.getName().equals(injectObj.getClass().getInterfaces()[0].getName())) {
                                    count++;
                                }
                            }
                            if (count > 1) {
                                throw new SchMoreOneBeanTypeException("根据类型自动匹配,数量不能大于1个");
                            }
                            if (count == 1) {
                                field.set(obj, injectObj);
                            }
                        }

                    } else if ("byName".equals(autowireType)) {
                        //根据名字自动装配
                        for (Field field : fieldList) {
                            field.setAccessible(true);
                            //属性类型
                            Class fieldClass = field.getType();
                            //属性名称
                            String name = field.getName();
                            Object injectObj = iocMap.get(name);
                            if (injectObj.getClass().getInterfaces()[0].getName().equals(fieldClass.getName())) {
                                field.set(obj, injectObj);
                            }
                        }

                    } else {

                    }

                }

                if (obj == null) {
                    obj = aClass.newInstance();
                }

                iocMap.put(beanId, obj);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }


    }


    public Object getBean(String beanName) {

        return iocMap.get(beanName);
    }


}
